﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using migradorWPF.Base;
using System.Data;
using System.Windows.Forms;
using System.Data.SqlClient;
using System.IO;
using migradorWPF.Models;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Reflection;
namespace migradorWPF.Services
{
    public class BDServiceADONET : BDService
    {
        #region members
        private DbConnection _conn;
        private string _typeDb;
        private String _host;
        private string _port;
        private string _nombreDb;
        private string _userDb;
        private string _passUserDb;
        private string _errorBaseDeDatos;
        private ObservableCollection<ParcheAplicado> _parchesAplicados;
        private string _dll;
        private string _dbCommandClass;
        private string _dbConnectionClass;
        private Assembly _DBassembly;
        private string _dbType;
        private string _dbTransaction;
        private bool _procedimientosAlmacenados;
        private bool _cancelarParches;
        private bool _aplicandoParches = false;                    
        #endregion

        #region properties
        public DbConnection Conn
        {
            get { return _conn; }
            set { _conn = value; }
        }

        override public string TypeDb
        {
            get { return _typeDb; }
            set { _typeDb = value; }
        }

        override public String Host
        {
            get { return _host; }
            set { _host = value; }
        }

        override public string Port
        {
            get { return _port; }
            set { _port = value; }
        }

        override public string NombreDb
        {
            get { return _nombreDb; }
            set { _nombreDb = value; }
        }

        override public string UserDb
        {
            get { return _userDb; }
            set { _userDb = value; }
        }

        override public string PassUserDb
        {
            get { return _passUserDb; }
            set { _passUserDb = value; }
        }

        override public string ErrorBaseDeDatos
        {
            get { return _errorBaseDeDatos; }
            set { _errorBaseDeDatos = value; }
        }

        override public ObservableCollection<ParcheAplicado> ParchesAplicados
        {
            get { return _parchesAplicados; }
            set { _parchesAplicados = value; }
        }

        public string Dll
        {
            get { return _dll; }
            set { _dll = value; }
        }

        public string DbCommandClass
        {
            get { return _dbCommandClass; }
            set { _dbCommandClass = value; }
        }

        public string DbConnectionClass
        {
            get { return _dbConnectionClass; }
            set { _dbConnectionClass = value; }
        }

        public Assembly DBassembly
        {
            get { return _DBassembly; }
            set { _DBassembly = value; }
        }

        public string DbType
        {
            get { return _dbType; }
            set { _dbType = value; }
        }

        public string DbTransaction
        {
            get { return _dbTransaction; }
            set { _dbTransaction = value; }
        }

        override public bool ProcedimientosAlmacenados
        {
            get { return _procedimientosAlmacenados; }
            set { _procedimientosAlmacenados = value; }
        }

        public bool CancelarParches
        {
            get { return _cancelarParches; }
            set { _cancelarParches = value; }
        }

        override public bool AplicandoParches
        {
            get { return _aplicandoParches; }
            set { _aplicandoParches = value; }
        }
        #endregion

        public BDServiceADONET(string baseDeDatos)
        {
            switch (baseDeDatos)
            {
                case Constantes.BasesDeDatosSoportadas.POSTGRESQL:
                    Dll = Constantes.Dll.POSTGRESQL;
                    DbCommandClass = Constantes.DbCommandClass.POSTGRESQL;
                    DbConnectionClass = Constantes.DbConection.POSTGRESQL;
                    DBassembly = Assembly.LoadFrom(Dll);
                    DbType = Constantes.BasesDeDatosSoportadas.POSTGRESQL;
                    break;
                case Constantes.BasesDeDatosSoportadas.Mysql5x:
                    Dll = Constantes.Dll.Mysql5x;
                    DbCommandClass = Constantes.DbCommandClass.Mysql5x;
                    DbConnectionClass = Constantes.DbConection.Mysql5x;
                    DBassembly = Assembly.LoadFrom(Dll);
                    DbType = Constantes.BasesDeDatosSoportadas.Mysql5x;
                    break;
                case Constantes.BasesDeDatosSoportadas.SQL_SERVER:
                    Dll = Constantes.Dll.SQL_SERVER;
                    DbCommandClass = Constantes.DbCommandClass.SQL_SERVER;
                    DbConnectionClass = Constantes.DbConection.SQL_SERVER;
                    DBassembly = Assembly.LoadFrom(Dll);
                    DbType = Constantes.BasesDeDatosSoportadas.SQL_SERVER;
                    break;
                case Constantes.BasesDeDatosSoportadas.Oracle10g11g:
                    Dll = Constantes.Dll.Oracle10g11g;
                    DbCommandClass = Constantes.DbCommandClass.Oracle10g11g;
                    DbConnectionClass = Constantes.DbConection.Oracle10g11g;
                    DBassembly = Assembly.LoadFrom(Dll);
                    DbType = Constantes.BasesDeDatosSoportadas.Oracle10g11g;
                    break;
            }
        }

        override public bool Conectar()
        {
            try
            {
                Type connectionType = DBassembly.GetType(DbConnectionClass);
                Conn = (DbConnection)Activator.CreateInstance(connectionType);
                Conn.ConnectionString = String.Format(getConectionString(), this.Host, this.Port, this.NombreDb, this.UserDb, this.PassUserDb);
                Conn.Open();
                if (this.Conn.State.Equals(ConnectionState.Open))
                {
                    return true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                this.ErrorBaseDeDatos = ex.Message;
                return false;
            }
            return false;
        }

        

        override public bool ObtenerParchesAplicados()
        {
            if (!this.ExisteConexion())
            {
                MessageBox.Show(Constantes.Mensages.ERROR_NO_EXISTE_CONEXION);
                return false;
            }

            try
            {
                String stringSQL = "SELECT nombre_parche, fecha_aplicacion, aplicado_por FROM sql_patches";
                Type commandType = DBassembly.GetType(DbCommandClass);
                DbCommand cmd = (DbCommand)Activator.CreateInstance(commandType, stringSQL, Conn);
                DbDataReader dr = cmd.ExecuteReader();
                ParchesAplicados = new ObservableCollection<ParcheAplicado>();
                while (dr.Read())
                {
                    ParchesAplicados.Add(new ParcheAplicado()
                    {
                        Nombre_parche = dr["nombre_parche"].ToString(),
                        Fecha_aplicacion = dr["fecha_aplicacion"].ToString(),
                        Aplicado_por = dr["aplicado_por"].ToString(),
                    });
                }
                dr.Close();
                return true;
            }
            catch (Exception ex)
            {
                Logger.log(ex.Message, Logger.Level.INFO);
                ParchesAplicados = null;
                return true; //significa que no existe la tabla parches
            }
        }

        override public bool ExisteTablaParches()
        {
            if (!this.ExisteConexion())
            {
                MessageBox.Show(Constantes.Mensages.ERROR_NO_EXISTE_CONEXION);
                return false;
            }

            try
            {
                string stringSQL = String.Empty;
                switch (DbType)
                {
                    case Constantes.BasesDeDatosSoportadas.POSTGRESQL:
                        stringSQL = "SELECT * FROM sql_patches LIMIT 1";
                        break;
                    case Constantes.BasesDeDatosSoportadas.Mysql5x:
                        stringSQL = "SELECT * FROM sql_patches LIMIT 1";
                        break;
                    case Constantes.BasesDeDatosSoportadas.SQL_SERVER:                    
                        stringSQL = "SELECT TOP 1 * FROM sql_patches";
                        break;
                    case Constantes.BasesDeDatosSoportadas.Oracle10g11g:
                        stringSQL = "SELECT * FROM sql_patches WHERE rownum = 1";
                        break;
                    default:
                        stringSQL = "SELECT * FROM sql_patches LIMIT 1";
                        break;
                }
                Type commandType = DBassembly.GetType(DbCommandClass);
                DbCommand cmd = (DbCommand)Activator.CreateInstance(commandType, stringSQL, Conn);
                DbDataReader dr = cmd.ExecuteReader();
                dr.Close();
                return true;
            }
            catch (Exception ex)
            {
                Logger.log(ex.Message, Logger.Level.ERROR);
                return false;
            }
        }

        override public bool CrearTablaParches()
        {
            if (!this.ExisteConexion())
            {
                MessageBox.Show(Constantes.Mensages.ERROR_NO_EXISTE_CONEXION);
                return false;
            }

            try
            {
                System.IO.StreamReader archivoParche = null;
                switch (DbType)
                {
                    case Constantes.BasesDeDatosSoportadas.POSTGRESQL:
                        archivoParche = new System.IO.StreamReader("./SqlTablaParches/PostgreSQL.sql");
                        break;
                    case Constantes.BasesDeDatosSoportadas.Mysql5x:
                        archivoParche = new System.IO.StreamReader("./SqlTablaParches/MySQL.sql");
                        break;
                    case Constantes.BasesDeDatosSoportadas.SQL_SERVER:
                        archivoParche = new System.IO.StreamReader("./SqlTablaParches/SqlServer.sql");
                        break;
                    case Constantes.BasesDeDatosSoportadas.Oracle10g11g:
                        archivoParche = new System.IO.StreamReader("./SqlTablaParches/Oracle.sql");
                        break;
                }
                
                string contenidoArchivoSQL = archivoParche.ReadToEnd();
                String stringSQL = contenidoArchivoSQL;
                Type commandType = DBassembly.GetType(DbCommandClass);
                DbCommand cmd = (DbCommand)Activator.CreateInstance(commandType);
                cmd.CommandText = stringSQL;
                cmd.Connection = this.Conn;
                int success = cmd.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                Logger.log(ex.Message, Logger.Level.ERROR);
                return false;
            }
            return true;
        }

        override public bool AplicarParches(ObservableCollection<ArchivoMigracion> archivo)
        {
            AplicandoParches = true;
            DbCommand cmd;
            Type commandType = DBassembly.GetType(DbCommandClass);
            string parcheActual = String.Empty;
            
            CancelarParches = false;
            System.IO.StreamReader archivoParche = null;

            

            foreach (var parche in archivo)
            {
                DbTransaction transaction = Conn.BeginTransaction();
                try
                {
                    if (CancelarParches)
                    {
                        throw new Exception("Cancelado por el usuario");
                    }

                    parcheActual = parche.Nombre;
                    archivoParche = new System.IO.StreamReader(parche.File.FullName, System.Text.Encoding.UTF8, true);
                    string contenidoArchivoSQL = archivoParche.ReadToEnd();

                    if (CancelarParches)
                    {
                        throw new Exception("Cancelado por el usuario");
                    }

                    // ejecutamos la consulta
                    cmd = (DbCommand)Activator.CreateInstance(commandType, contenidoArchivoSQL, Conn);
                    cmd.Transaction = transaction;
                    cmd.ExecuteNonQuery();

                    if (CancelarParches)
                    {
                        throw new Exception("Cancelado por el usuario");
                    }

                    //actualizamos la tabla parches, si no son procedimientos almacenados
                    if (!this.ProcedimientosAlmacenados)
                    {
                        string actualizarTablaParches = String.Format("insert into sql_patches (nombre_parche, fecha_aplicacion, aplicado_por) values ('{0}','{1}','{2}')", parche.File.Name, DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"), System.Environment.MachineName);
                        cmd = (DbCommand)Activator.CreateInstance(commandType, actualizarTablaParches, Conn);
                        cmd.Transaction = transaction;
                        cmd.ExecuteNonQuery();                       
                    }

                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    transaction.Rollback();
                    this.ErrorBaseDeDatos = "Error al ejecutar \"" + parcheActual + "\": " + ex.Message;

                    if (CancelarParches)
                    {
                        this.ErrorBaseDeDatos = "Cancelado por el usuario";
                    }

                    AplicandoParches = false;
                    return false;
                }
                finally
                {
                    archivoParche.Close();
                }
            }

            AplicandoParches = false;
            this.ErrorBaseDeDatos = "Todos los parches fueron ejecutados satisfactoriamente.";
            return true;
        }

        override public void CancelarAplicarParches()
        {
            CancelarParches = true;
        }

        override public bool ExisteConexion()
        {
            try
            {
                if (this.Conn.State.Equals(ConnectionState.Closed))
                {
                    return false;
                }
                if (this.Conn.State.Equals(ConnectionState.Open))
                {
                    Conn.Close();
                }
                Conn.Open();
                if (this.Conn.State.Equals(ConnectionState.Open))
                {
                    return true;
                }
            }
            catch (Exception ex)
            {
                Logger.log(ex.Message, Logger.Level.ERROR);            
            }
            return false;
        }

        private string getConectionString()
        {
            switch (DbType)
            {
                case Constantes.BasesDeDatosSoportadas.POSTGRESQL:
                    return Constantes.StringDeConeccion.POSTGRESQL;
                case Constantes.BasesDeDatosSoportadas.Mysql5x:
                    return Constantes.StringDeConeccion.Mysql5x;
                case Constantes.BasesDeDatosSoportadas.SQL_SERVER:                
                    return Constantes.StringDeConeccion.SQL_SERVER;
                case Constantes.BasesDeDatosSoportadas.Oracle10g11g:
                    return Constantes.StringDeConeccion.Oracle10g11g;
                default:
                    return "";
            }
        }

        override public bool Desconectar()
        {
            try
            {
                this.Conn.Close();
                return true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;
            }
        }
    }
}
